/*=====================================================================*
 *                   Copyright (C) 2011 Paul Mineiro                   *
 * All rights reserved.                                                *
 *                                                                     *
 * Redistribution and use in source and binary forms, with             *
 * or without modification, are permitted provided that the            *
 * following conditions are met:                                       *
 *                                                                     *
 *     * Redistributions of source code must retain the                *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer.                                       *
 *                                                                     *
 *     * Redistributions in binary form must reproduce the             *
 *     above copyright notice, this list of conditions and             *
 *     the following disclaimer in the documentation and/or            *
 *     other materials provided with the distribution.                 *
 *                                                                     *
 *     * Neither the name of Paul Mineiro nor the names                *
 *     of other contributors may be used to endorse or promote         *
 *     products derived from this software without specific            *
 *     prior written permission.                                       *
 *                                                                     *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND              *
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,         *
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES               *
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE             *
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER               *
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,                 *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES            *
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE           *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR                *
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF          *
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT           *
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY              *
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE             *
 * POSSIBILITY OF SUCH DAMAGE.                                         *
 *                                                                     *
 * Contact: Paul Mineiro <paul@mineiro.com>                            *
 *=====================================================================*/

#ifndef __GET_MODEL_HH__
#define __GET_MODEL_HH__

#include <map>
#include <memory>
#include <string>
#include "commandline.hh"
#include "model.hh"

namespace playerpiano
{
  template<typename T>
  bool
  ignoring (std::map<std::string, const char*>& args,
            T                                   arg_specs,
            Model*                              model);

  template<>
  bool
  ignoring (std::map<std::string, const char*>&,
            Null,
            Model*)
    {
      return false;
    }

  template<typename T>
  bool
  ignoring (std::map<std::string, const char*>& args,
            ArgSpec<T>                          arg_specs,
            Model*                              model)
    {
      if (! model->lookup<void*> (arg_specs.name).first)
        {
          std::cerr << "ERROR: model is missing parameter '"
                    << arg_specs.name
                    << "'"
                    << std::endl;

          return true;
        }

      if ((args.find ("--quiet") == args.end ()) &&
          args[arg_specs.name] && 
          strcmp (model->lookup<char*> (arg_specs.name).first,
                  args[arg_specs.name]) != 0)
        {
          std::cerr 
            << "WARNING: overriding command line specification '"
            << args[arg_specs.name]
            << "' for argument '"
            << arg_specs.name
            << "' using model file value '"
            << model->lookup<char*> (arg_specs.name).first
            << "'"
            << std::endl;
        }

      return false;
    }

  template<typename T>
  bool
  ignoring (std::map<std::string, const char*>& args,
            DefaultValue<T>                     arg_specs,
            Model*                              model)
    {
      return ignoring (args, arg_specs.payload, model);
    }

  template<typename Head,
           typename Tail>
  bool
  ignoring (std::map<std::string, const char*>& args,
            std::pair<Head, Tail>               arg_specs,
            Model*                              model)
    {
      return ignoring (args, arg_specs.first, model) || 
             ignoring (args, arg_specs.second, model);
    }

  template<typename T>
  void
  ensure (std::map<std::string, const char*>& args,
          T                                   arg_specs,
          Model*                              model);

  template<>
  void
  ensure (std::map<std::string, const char*>&,
          Null,
          Model*)
    {
    }

  template<typename T>
  void
  ensure (std::map<std::string, const char*>& args,
          ArgSpec<T>                          arg_specs,
          Model*                              model)
    {
      model->ensure (arg_specs.name, 1 + strlen (args[arg_specs.name]));
    }

  template<typename T>
  void
  ensure (std::map<std::string, const char*>& args,
          DefaultValue<T>                     arg_specs,
          Model*                              model)
    {
      ensure (args, arg_specs.payload, model);
    }

  template<typename Head,
           typename Tail>
  void
  ensure (std::map<std::string, const char*>& args,
          std::pair<Head, Tail>               arg_specs,
          Model*                              model)
    {
      ensure (args, arg_specs.first, model);
      ensure (args, arg_specs.second, model);
    }

  template<typename T>
  void
  persist (std::map<std::string, const char*>& args,
           T                                   arg_specs,
           Model*                              model);

  template<>
  void
  persist (std::map<std::string, const char*>&,
           Null,
           Model*)
    {
    }

  template<typename T>
  void
  persist (std::map<std::string, const char*>& args,
           ArgSpec<T>                          arg_specs,
           Model*                              model)
    {
      strcpy (model->lookup<char*> (arg_specs.name).first,
              args[arg_specs.name]);
    }

  template<typename T>
  void
  persist (std::map<std::string, const char*>& args,
           DefaultValue<T>                     arg_specs,
           Model*                              model)
    {
      persist (args, arg_specs.payload, model);
    }

  template<typename Head,
           typename Tail>
  void
  persist (std::map<std::string, const char*>& args,
           std::pair<Head, Tail>               arg_specs,
           Model*                              model)
    {
      persist (args, arg_specs.first, model);
      persist (args, arg_specs.second, model);
    }

  template<typename T>
  Model*
  get_model (std::map<std::string, const char*>& args,
             T                                   arg_specs)
    {
      bool test_only = (args.find ("--test_only") != args.end ());
      struct stat buf;
      bool exists = (stat (args["--model"], &buf) == 0);

      if (! exists)
        {
          if (required (args, arg_specs))
            {
              return 0;
            }
        }

      Model* model = 
        new Model (Model::Open (args["--model"]).read_only (test_only));

      if (model)
        {
          if (exists)
            {
              if (ignoring (args, arg_specs, model))
                {
                  return 0;
                }
            }
          else
            {
              ensure (args, arg_specs, model);
              persist (args, arg_specs, model);
            }
        }

      return model;
    }

  Model*
  get_model (std::map<std::string, const char*>& args)
    {
      if (required (args,
                    ArgSpec<NonEmptyString> ("--model",
                                             NonEmptyString ())))
        {
          return 0;
        }

      struct stat buf;
      bool exists = (stat (args["--model"], &buf) == 0);
      const char* strategy;
      std::auto_ptr<Model> model;

      if (exists)
        {
          const char* pathname = args["--model"];
          model = std::auto_ptr<Model> (new Model (Model::Open (pathname).read_only (true)));
          strategy = model->lookup<const char*> ("--strategy").first;

          if (! strategy)
            {
              std::cerr << "ERROR: invalid model file '"
                        << args["--model"]
                        << "': --strategy value not found in file"
                        << std::endl;
              return 0;
            }
        }
      else
        {
          if (required (args,
                        ArgSpec<NonEmptyString> ("--strategy",
                                                 NonEmptyString ())))
            {
              return 0;
            }

          strategy = args["--strategy"];
        }

      if (strcmp (strategy, "nominal") == 0)
        {
          return
            get_model (args,
                       arg_list ()
                        .add ("--strategy", ConstantString ("nominal"))
                        .add ("--n_items", PositiveInteger (1))
                        .add ("--n_labels", PositiveInteger (2))
                        .add ("--n_worker_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .add ("--n_feature_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .value);
        }
      else if (strcmp (strategy, "ordinal") == 0)
        {
          return
            get_model (args,
                       arg_list ()
                        .add ("--strategy", ConstantString ("ordinal"))
                        .add ("--n_items", PositiveInteger (1))
                        .add ("--n_labels", PositiveInteger (2))
                        .add ("--n_worker_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .add ("--n_feature_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .value);
        }
      else if (strcmp (strategy, "nominalembed") == 0)
        {
          return
            get_model (args,
                       arg_list ()
                        .add ("--strategy", ConstantString ("nominalembed"))
                        .add ("--n_items", PositiveInteger (1))
                        .add ("--n_labels", PositiveInteger (2))
                        .add ("--n_worker_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .add ("--n_feature_bits", PositiveInteger (8, 32))
                          .default_value ("16")
                        .add ("--n_dims", 
                              PositiveIntegerLessThan ("--n_labels", 
                                                       args["--n_labels"]))
                          .default_value ("2")
                        .add ("--seed", PositiveInteger (1))
                          .default_value ("69")
                        .value);
        }
      else
        {
          std::cerr << "ERROR: unrecognized strategy '" 
                    << args["--strategy"] 
                    << "'" 
                    << std::endl;
          std::cerr << "try --help" << std::endl;
          return 0;
        }
    }
}

#endif // __GET_MODEL_HH__
